$:.unshift File.join(File.dirname(__FILE__), "..", "lib")

require 'EventSet'
require 'Event'



#Using Rupeas you create a new Rupeas object and add your DSL code
#Start by specifying the events in the event log with the Type method
#The the Log is loaded with method loadlog
module RupeasFunctions

  def savelog(eventset, filename)
   puts "Saving to #{filename}" if $DEBUG
   if eventset.empty?
     puts "Nothing to save for empty eventset" if $DEBUG
     return
   else 
     types = Array.new()
     typedef = Hash.new()
     puts "savelog" if $DEBUG
     begin
        description = File.open(filename + '.red', 'w')
        log = File.open(filename + '.log', 'w')
     rescue IOError
      $stderr.puts "Error when opening files to write"
     end      
     eventset.each do |event|
       if !types.include?(event[:type])
         types << event[:type]
         #Add sorted key but removing the type key, since it will be first
         thisevent = Hash.new
         p event.class.class_eval {@constraints} if $DEBUG
         keys = event.keys.sort!.to_a.reject! {|key| key.eql?(:type)}
         keys.each {|key| thisevent[key] = event.class.class_eval {@constraints[key][:fieldtype]}}
         typedef[event[:type]]= thisevent
       end
         string = event[:type].to_s
         typedef[event[:type]].keys.sort!.each {|key| string += " " + event[key].to_s}
         string += "\n"
         log.write(string)
     end
     log.close
     types.sort!
     description.write("Rupeas.new do\n")
     types.each do |type|
        description.write("  Type :#{type} do\n")
        typedef[type].each {|key, value| description.write("    with :name => :#{key}, :fieldtype => :#{value}\n")}
        description.write("  end\n")
     end
     description.write("end")   
     description.close
   end
  end

  def transform(iterative_indication=nil)
      #TODO: Determine why caller function returns somethimes the same for caller(1) and caller(0)
      a = Proc.new
      currentItems = a.arity
      puts  "This block features #{a.arity} variables" if $DEBUG
      if iterative_indication == :iterative
        puts 'iterative'
      elsif iterative_indication==nil
        puts 'no iteration required' if $DEBUG
      else #old style
        puts 'yes'
      end
      puts "caller(0)"   if $DEBUG
      list = caller(0)
      0.upto(2) {|item| p list[item]} if $DEBUG
      puts "caller(1)"        if $DEBUG
      list = caller(1)
      0.upto(2) {|item| p list[item]}  if $DEBUG

      file, targetline = caller(0)[1].split(':') 
#      puts file, targetline
      readfromfile = open(file,'r')
      arguments = Array.new
      constraints = Array.new
      selectors = Array.new
      options = Hash.new
      lineno = 0
      current_constraint = String.new
      readarray = readfromfile.readlines
      
      if readarray[targetline.to_i-1] =~ /transform(\(:iterative\))?\sdo\s+\|\s*((\w)+)((\s*,\s*(\w)+)*)\s*\|\s*$/
        puts $& if $DEBUG
        #$1 = iterative argument?
        #$2 first argument
        #$3 last letter of first argument
        #$4 complete parameter list
        #puts 'non-iterative' if $1 == nil
        arguments << $2.strip
        $4.split(',').each {|arg| arguments << arg.strip unless arg.empty?}
        puts 'Performing transform on variables: ' + arguments.to_s if $DEBUG
      else
        puts 'Transformation error in ' +  readarray[targetline.to_i]
        puts "File #{file}; line #{targetline}"  if $DEBUG
      end
      
      scanarray = readarray[targetline.to_i, readarray.length-1]
      
      scanarray.each do |line|
        if line =~ /^\s*constraint\s+((\d|\w|\s|=|>|<|:|\[|\])+)\s*\s*($|#)/
          constraints <<  $1.strip
        elsif line =~ /^\s*select\s+((\d|\w|\s|=|>|<|:|\[|\]|,)+)\s*\s*($|#)/
          selectors << $1.strip
        elsif line =~ /^\s*(\w+)\s+((\d|\w|\s|=|>|<|:|\[|\]|,)+)\s*\s*($|#)/ and $1.strip != 'end'
          if options.has_key?($1.strip) 
            $stderr.puts "Aborting : new events should be named uniquely not the same as for #{$1.strip}"
            Process.exit!
          end

          options[$1.strip] = $2.strip
          puts "options are #{options}" if $DEBUG
          
        elsif line =~ /^\s*end\s*/
          break
        end

      end
      argumentstring = String.new
      arguments.each do |argument|
        argumentstring += argument + ','
      end
      argumentstring.chop!

      partitions, constraints = process_constraints(currentItems, arguments, constraints)
      puts "constraints on sets #{constraints.each {|constraint| p constraint}}" if $DEBUG
      puts "selector constraints are #{selectors.each {|selector| p selector}}" if $DEBUG
      puts "options for transformation #{options.each {|option| p option}}" if $DEBUG
      puts "arguments are #{arguments.each {|argument| p argument}}" if $DEBUG
      puts "current items are #{currentItems.to_s}" if $DEBUG
      puts "partitions are #{partitions.each {|part| p part}}" if $DEBUG
      resultset = EventSet.new
      if partitions.empty?
        resultset = self.process_event_set(constraints, selectors, argumentstring, options, currentItems)      
      else
        partitions.each do |partition|
          select_symbol = partition.to_sym
          puts "self.values(#{select_symbol})" if $DEBUG
          thesevalues = self.values(select_symbol)
          #puts "values to subset to"
          puts thesevalues.length if $DEBUG
          thesevalues.each do |value|
            subset = self[select_symbol=> value]
            #p "subset processing"
            #p subset.length
            resultset = resultset.union(subset.process_event_set(constraints, selectors, argumentstring, options, currentItems))
            puts "completed #{(thesevalues.index(value) / 1000).to_i} thousand runs" if thesevalues.index(value) % 1000 == 0 and thesevalues.index(value) / 1000 > 0
          end #thesevalues
        end #partitions iterator
      end #if
    puts "resultset length is #{resultset.length}" if $DEBUG
    return resultset
  end #new_transform

  def process_event_set(constraints, selectors, argumentstring, options, currentItems)
    eventset = self
    #reconstruct a full predicate expression
    predicatestring = String.new
    
    unless constraints.empty?
      constraints.each do |constraint|
        predicatestring += constraint + " and "
      end
      if selectors.empty?
         predicatestring.chomp!(" and ")
      else 
        predicatestring += "("
      end
    end

    unless selectors.empty?
      selectors.each do |select|
        predicatestring += select + " or "
      end
      predicatestring.chomp!(" or ")
      predicatestring += ")" unless constraints.empty?
    end
    
    puts "eventset = eventset.predicate(currentItems) {|#{argumentstring}| #{predicatestring} }" if $DEBUG
    eval "eventset = eventset.predicate(currentItems) {|#{argumentstring}| #{predicatestring} }"


    if options.length == 1
      puts "single option" if $DEBUG
      newevent = ""
      options.each do |option|
        action, newevent = option
      end
      events = "{\n #{newevent} \n}"
    else             
      #create_new_event
      events = "[\n"
      options.each do |option|
        action, newevent = option
        events += "{ #{newevent} },"
      end
      events.chomp!("")
      events += "\n]"
    end
    
    puts "set_transform(eventset, :merge, currentItems) do |#{argumentstring}| \n #{events} \nend" if $DEBUG
    result = eval "set_transform(eventset, :merge, currentItems) do |#{argumentstring}| \n #{events} \nend"

    return result
  end

  
private
  #TODO: common error handling : set flag that gets processed at the end or so
  def process_constraints(currentItems, arguments, constraints)
    puts "rupeas::process_constraints" if $DEBUG
    filter = Array.new
    partitions = Array.new
    new_constraints = Array.new
    constraints.each do |constraint|
      if constraint =~ /([a-zA-Z]+\[:[a-zA-Z]+\])(\W*)(:[a-zA-Z]+|[a-zA-Z]+\[:[a-zA-Z]+\])/
        partition = constraint_parse($1.strip, $2.strip, $3.strip, arguments)
      else
        $stderr.puts "Parse Error in constraint #{constraint}"
        $stderr.puts "Aborting"
        Process.exit!
      end
      if partition
         partitions << partition
      else
        new_constraints << constraint
      end
    end
    #puts "process_constraints"
    #p partitions
    #p new_constraints
    return [partitions, new_constraints]
  end

  #Should be private
  #Checks constraints : Can we partition and yes on which field?
  def constraint_parse(term0, compare, term1, arguments)

    terms = Array.new
    terms << term0 <<term1
    case compare
    when "=="
      type = :equal
    when ">=", "<=", ">", "<"
      type = :compare
    else 
      $stderr.puts "Parse Error in terms #{terms} for compare operator #{compare}"
      $stderr.puts "Aborting"
      Process.exit!
    end

    return_hash = terms_extract(terms)
    variables = return_hash[:var]
    fields = return_hash[:fields]    
#    p type
#    p variables
#    p fields
    if variables.length == 2 and variables[0] != variables[1] and fields[0] == fields[1] and type == :equal
      #we can partition
      #puts "yes - partition on #{fields[0]}"
      partition = fields[0]
    else
      partition = nil
    end
    puts "constraint_parse" if $DEBUG
    p partition if $DEBUG
    return partition
    
  end
  
  #Should be private
  #Checks terms for form and determines kind of assignment
  def terms_extract(terms)
    return_hash = Hash.new
    variables = Array.new
    fields = Array.new
    terms.each do |term|
      if term =~ /(^:[a-zA-Z]+)$/
        #do nothing
      elsif term =~ /^([a-zA-Z]+)\[:([a-zA-Z]+)\]/
        variables << $1.strip
        fields << $2.strip
      else 
        $stderr.puts "Error in term #{term}"
      end
    end
    return_hash[:var] = variables
    return_hash[:fields] = fields
    return return_hash
  end
  
    #ksubn is adapted from "Combinatorial Algorithms: For computers and calculators, Second Edition; Academic Press 1978"
    #Note that ksubn takes all events and produces every possible subset (permutation without order) of k events from the baseset
    #for example k=3, baseset = 5 events produces 10 event subsets with permutations (in a set order does not count)
    def ksubn(k)
      finished = false
      init = true
      n=self.length-1
  #    puts 'length is ' + n.to_s
      b=self.to_a
      left=self.dup
      a = Array.new()
      1.upto(k) {|x| a << x}

      i=0
      h=0
      final = Array.new()
      max_tries = 1000
      while not(finished) && i<max_tries:   
        out = EventSet.new()
        i=i+1
        if init
          m=0
          h=k-1
        else
          #B
          #puts 'm: ' + m.to_s
          #puts 'n-h: ' + (n-h).to_s
          if not(m>=n-h)
          	h=-1
          end
          #C
          h=h+1
          #puts 'h: '+ h.to_s
          #puts 'a['+(k+1-h-2).to_s+'] = ' + (a[k+1-h-2]).to_s
          m=a[k+1-h-2]+1
          #puts 'm: ' + m.to_s
        end
        #puts 'h: '+h.to_s
        for j in (0...h+1)
          #puts 'j = '+ j.to_s
          a[k+j-h-1] = m+j
          #puts 'a['+(k+j-h-1).to_s+'] = ' + 'b[' + (m+j).to_s + ']='+(m+j).to_s
        end
        init = false

        finished = true if a[0]==n-k+1

        #puts 'a is ' + a.to_s      
        for o in (0...k)
          out.add(b[a[o]])
          left.delete(b[a[o]])
        end
        #puts 'this set is ' + out.to_s + "\n"
        final << out
      end
      #puts 'final set is ' + final.to_s
      return final, left
    end

    #This is adapted from "Combinatorial Algorithms: For computers and calculators, Second Edition; Academic Press 1978"
    #This is the iterator variant of ksubn, which provides an iterator to be used with a block
    #i.e. each yield returns one subset (permutation) to an outer block
    def ksubn_iterator(k)
      finished = false
      init = true
      n=self.length-1
  #    puts 'length is ' + n.to_s
      b=self.to_a
      a = Array.new()
      1.upto(k) {|x| a << x}
      i=0
      h=0

      max_tries = 1000
      while not(finished) and i<max_tries:
        out = EventSet.new()
        i=i+1
        if init
          m=0
          h=k-1
        else
          #B
          #puts 'm: ' + m.to_s
          #puts 'n-h: ' + (n-h).to_s
          if not(m>=n-h)
          	h=-1
          end
          #C
          h=h+1
          #puts 'h: '+ h.to_s
          #puts 'a['+(k+1-h-2).to_s+'] = ' + (a[k+1-h-2]).to_s
          m=a[k+1-h-2]+1
          #puts 'm: ' + m.to_s
        end
        #puts 'h: '+h.to_s
        for j in (0...h+1)
          #puts 'j = '+ j.to_s
          a[k+j-h-1] = m+j
          #puts 'a['+(k+j-h-1).to_s+'] = ' + 'b[' + (m+j).to_s + ']='+(m+j).to_s
        end
        init = false

        finished = true if a[0]==n-k+1
        puts "target is #{n-k+1} and i am #{a[0]}" if $DEBUG
        #puts 'a is ' + a.to_s      
        for o in (0...k)
          out << b[a[o]]
        end
        #puts 'this set is ' + out.to_s + "\n"
        yield out
      end
    end

    #This is adapted from "The Algorithm Design Manual" by Steven S. Skiena
    #Namely it builds partial tress of the permutations
    # See "Backtracking" and "Constructing all Permutations" in Chapter "Combinatorial Search and Heuristic Methods"
    # It is based on a tree representation of the solutions (and partial solutions) and uses DFS
    def permutations(event_array)
    # I did not want to use a recursive method so I went with an iterative approach
      finalsolutions = Array.new()
      #create sets of possible items to select for the permutations
      candidate_sets = Array.new()
      current_set = Array.new()
      candidate_sets << event_array.clone
      k = 1
      while k > 0
        while !candidate_sets[k-1].empty?
          #note that pop removes currentitem from the current candidate set
          currentitem = candidate_sets[k-1].pop
          current_set << currentitem
          if k == event_array.length()
            finalsolutions << current_set.clone
            current_set.pop
            puts "Updated final solution - now is  #{finalsolutions}" if $DEBUG
            break
          end
          k= k + 1
          candidate_sets[k-1] = event_array.clone
          for item in current_set
            #Cannot use these since the are not eql but they are == because of the clone
            #candidate_sets[k-1].delete(item)
            candidate_sets[k-1].delete_at(candidate_sets[k-1].index(item))
          end
          puts "new candiate set is #{candidate_sets[k-1]}" if $DEBUG
        end
        k = k-1
        current_set.pop
      end
      puts "final solutions are: #{finalsolutions}" if $DEBUG
      return finalsolutions
    end

    #This is adapted from "The Algorithm Design Manual" by Steven S. Skiena
    #Namely it builds partial tress of the permutations
    # See "Backtracking" and "Constructing all Permutations" in Chapter "Combinatorial Search and Heuristic Methods"
    # It is based on a tree representation of the solutions (and partial solutions) and uses DFS
    #Here I work on integers and produce the sets only in the last stage
    def fast_permutations(event_array)
    # I did not want to use a recursive method so I went with an iterative approach
      finalsolutions = Array.new()
      #create sets of possible items to select for the permutations
      candidate_sets = Array.new()
      current_set = Array.new()

      finishlength = event_array.length
      intarray = Array.new
      (0...finishlength).each {|x| intarray << x}

      candidate_sets << intarray.clone
      k = 1
      while k > 0
        while !candidate_sets[k-1].empty?
          #note that pop removes currentitem from the current candidate set
          currentitem = candidate_sets[k-1].pop
          current_set << currentitem
          puts "Current item is #{currentitem}, current_set is #{current_set}" if $DEBUG

          if k == event_array.length()
            finalsolutions << current_set.clone
            current_set.pop
            puts "Updated final solution - now is  #{finalsolutions}" if $DEBUG
            break
          end
          k= k + 1
          candidate_sets[k-1] = intarray.clone
          for item in current_set
            candidate_sets[k-1].delete(item)
          end
          puts "new candiate set is #{candidate_sets[k-1]}" if $DEBUG
        end
        k = k-1
        current_set.pop
      end

      candidate_sets.clear()
      finalsolutions.each do |integerarray|
        current_set.clear()      
        integerarray.each do |intvalue|
          current_set << event_array[intvalue]
        end
        candidate_sets << current_set.clone
      end
      return candidate_sets
    end


  #Only available starting at ruby 1.8.7
  #  #Permutation for event_arrays based on ruby's array permutation
  #  def ruby_permutations(event_array)
  #  # I did not want to use a recursive method so I went with an iterative approach
  #    return event_array.permutation.to_a
  #  end

    def select(&block)
      returnSet = EventSet.new
      unless self.empty?
        self.each do |event|
          returnSet.add(event) if yield event
        end
      end
      return returnSet
    end
public
    #predicate takes an event set and returns a hash with two elements
    #It uses parameter k to determine the number of events to match the predicate
    #predicate returns:
    #1) :matched, which is an array of event subsets of size k matching the predicate
    #2) :unmatched, which is an eventset containing all events that did not match the predicate
    def predicate(k) 
      puts "EventSet::predicate(#{k})" if $DEBUG  
      matched = Array.new()
      unmatched = self.dup
      return_hash = Hash.new()
      if self.empty? or self.length < k
        return_hash[:matched] = matched
        return_hash[:unmatched] = unmatched
        return return_hash
      else
        ksubn_iterator(k) do |x|
          begin
            if k == 1
              matched << x.to_a if yield x.to_a[0]
            else
            #In this loop we have to enhace to allow for checking all different 
            #orderings of the subset
              #need to unshift the permutations
              begin
                   add_arrays =  fast_permutations(x.to_a)
                   add_arrays.each do |element|
                     if yield element
                       matched << element
                       puts "Currently Adding #{element}" if $DEBUG
                     end
                   end
              end
            end
          #If x is Nil...
          rescue NoMethodError
            puts "EventSet::predicate(#{k}):NoMethodError" if $DEBUG
            matched << Array.new if matched.empty?
          end
        end
        puts "EventSet::predicate(#{k}):MatchProcessing" if $DEBUG    
        #If everythin went through, feed back original events
        #for the new events I have to deep copy the original ones
        puts "process unmatched for matched #{matched}" if $DEBUG
        matched.each do |eventArray|
          eventArray.each do |element|
            if unmatched.include?(element)
              unmatched.delete(element)
            end
          end
        end
      end
     #TODO: check if this hurts anything - see test_predicate_empty_set
     #removed it for the DSL implementation where transform would fail due to this
  #    matched << Array.new if matched.empty?
      return_hash[:matched] = matched
      return_hash[:unmatched] = unmatched
      return return_hash
    end            



  #Transform is a method used by the Rupeas Set transformator
  #It takes as inputs the results from the predicate, i.e.
  #an array of matched event subsets
  #It takes as input how the event transformyation should be performed (:create, :new)
  #It takes the numbe rof events as a given parameter
    def set_transform(filteredset,type,k)
      puts "EventSet::set_transform processing #{filteredset[:matched].length} matched elements" if $DEBUG
      if filteredset[:matched].empty?
        @processed = false
        return filteredset[:unmatched]
      else
        resultSet = EventSet.new
        filteredset[:matched].each do |predicateevents|
          puts "Processing predicateevents #{predicateevents} \n ---------" if $DEBUG
          if predicateevents
            if predicateevents.length > 1
              processedelements = yield(predicateevents)
            elsif !predicateevents.empty?
              processedelements = yield(predicateevents[0])
            end
          end

          #TODO: it did not work wihtout to_s and using strings in the when clause
          #Why?? Using strings it works, but seems to be quite ugly
            if processedelements.kind_of?(Array):
                puts "Processing Array" if $DEBUG
                processedelements.each do |event|
                  processedevent = Event.new(:nodeid => 'transform', :type=>:processed)
                  event.each do |x,y| 
                     processedevent[x]=y
                  end
                  match = false
                  resultSet.each do |event|
                    if event == processedevent
                       match = true
                       break
                    end
                  end
                  resultSet.add(processedevent) unless match == true
                end
            elsif processedelements.kind_of?(Hash): 
              puts "Processing Hash" if $DEBUG
              processedevent = Event.new(:nodeid => 'transform', :type=>:processed)
              processedelements.each {|x,y| processedevent[x]=y}
              match = false
              resultSet.each do |event|
                if event == processedevent
                   match = true
                   break
                end
              end
              if match == false
               resultSet.add(processedevent)
               #The first event that is different from any existing causes @processed to be true
               @processed = true
              end
            elsif !processedelements
            else puts "something else #{processedelements.class}"
          end

          if type == :create:
            puts 'EventSet::set_transform::create' if $DEBUG
            #May need to check here if event already exists as above
            #Create changes the event set since it gets bigger
            #TODO: Technically there could be some issues like with meerge but create should not be used with iterate anyway
            predicateevents.each {|events| resultSet.add(events)}
            @processed = true
          end
        end
      return resultSet.union(filteredset[:unmatched])
      end

    end

    #Fixed point processor operator.
    #Yields to a block given and evaluates if the passed eventset is still changing
    #If no more changes occur, passes the result set.
    def iterative
      returnset = self.deep_clone
      begin
        @processed = false
        processedset  = returnset
        begin
          returnset = yield returnset
        rescue NoMethodError
          break
        rescue LocalJumpError
          return self
        end
        puts returnset.length if $DEBUG
      end while @processed or !(returnset == processedset)
      return returnset
    end


end

class Rupeas
  @@currentItems = 0

  include RupeasFunctions
  
  #Initializes a rupeas analysis. The block passed to rupeas is the analysis of eventfiles.
  #This includes a description of event formats, loading of event files and subsequenlty
  #processing the event sets.
  def initialize(&block)
    p self
    @ThisEventSet = EventSet.new
    @events = Hash.new
    #this reads in the whole block in the rupeas definition
    instance_eval(&block)
  end

    
  #Type specifies one event that is available in the analysis. The myid specifies its name.
  #In turn rupeas creates and event class with name "Myid" and each event has the type ":myid" (as a symbol).
  #The block contains further specification of this specific event. All events of this type have the same format.
  #Minimally the nodeid of the event has to be specified!
  def Type(myid, &block)
      eval "class #{myid.to_s.capitalize} < Event; @type = \"#{myid}\".to_sym;  def initialize(args); super([\"#{myid}\".to_sym].concat(args)); end; end"
      p "class #{myid.to_s.capitalize} < Event; @type = \"#{myid}\".to_sym;  def initialize(args); super([\"#{myid}\".to_sym].concat(args)); end; end" if $DEBUG
      #works along these lines
      #class Senddone < Event
      #  @@type = :senddone
      #  def initialize(args)
      #    super([@@type, args])
      #  end
      #end
      #Cache the access to the new class so we do not need to eval later with a string
      puts "#{myid.to_s.capitalize}.instance_eval(&block)" if $DEBUG
      @events[myid.to_s.capitalize] = eval "#{myid.to_s.capitalize}.instance_eval(&block)"
  end
  
  #Loads events from the file specified into an eventset, which can be subsequently processed.
  #Accepts all events previously specified with the Type method.
  def loadlog(filename)
   puts "loadlog"
   begin
      File.open(filename).each do |line|
        eventarray = line.split
    
        #removes type filed and uses it as identifier in eval
        typeIdentifier = eventarray.shift.capitalize
        #create new event from event subclass of type :type
        newevent = @events[typeIdentifier].new(eventarray)
        @ThisEventSet.add(newevent)
      end

    #This could be built in if checks are really needed but better search needs to be done
    #for objects, else it takes way too long
    #i=0
    #while i < @ThisEventSet.length
    #  j = 0
    #  reference = @ThisEventSet[i]
    #  while j < @ThisEventSet.length
    #    @ThisEventSet.delete_at(j) if reference ==  @ThisEventSet[j]
    #  end
    #end
    
    
    return @ThisEventSet
  # rescue
  #  $stderr.puts "Error for event in line #{i} of file #{filename}"
   end
  end
  
end

class EventSet
  include RupeasFunctions
end

